15重走我的Java Day15:当我的代码突然有了“读心术”:反射和注解的魔法之旅
15重走我的Java Day15:当我的代码突然有了“读心术”:反射和注解的魔法之旅
第一次看到同事用
@Autowired时,我以为这是某种魔法。直到我自己写测试时,面对一个300行的方法无从下手,才明白:好的代码不仅要能运行,还要能被测试、被扩展、被理解。
一、单元测试:从“自信满满”到“小心翼翼”

1.1 我以为我的代码坚如磐石
“测试?我代码写完自己跑一遍不就得了!”——这是我刚开始学习编程时的真实想法。
直到那个周五晚上11点,我提交了一段“完美”的订单处理代码:
java
public class OrderService {
// 我自豪的“高效”方法 - 整合了所有业务逻辑
public OrderResult processOrder(Order order, User user, Payment payment) {
// 验证订单(100行)
if (!validateOrder(order)) {
throw new ValidationException("订单无效");
}
// 检查库存(80行)
if (!checkInventory(order.getItems())) {
throw new InventoryException("库存不足");
}
// 处理支付(120行)
PaymentResult paymentResult = processPayment(payment, order.getTotal());
if (!paymentResult.isSuccess()) {
throw new PaymentException("支付失败");
}
// 更新库存、生成物流、发送通知...(200行)
// ...
return new OrderResult(true, "订单处理成功");
}
}
周一早上,运维的电话把我从梦中惊醒:“你的代码把测试环境的数据库锁死了!”
原来,我的“高效”方法:
- 无法单独测试:想测支付逻辑?必须构造完整的订单
- 错误难以定位:出问题时不知道是哪部分逻辑有问题
- 运行缓慢:每次测试都要走完整流程
1.2 JUnit教会我的“测试思维”
导师给我看了他的测试代码:
java
// 测试一个简单的计算器
class CalculatorTest {
private Calculator calculator;
@BeforeEach // 每个测试前都会执行
void setUp() {
calculator = new Calculator();
System.out.println("准备测试环境...");
}
@Test
@DisplayName("测试加法:2 + 3 = 5")
void testAdd() {
// 准备测试数据
int a = 2;
int b = 3;
// 执行被测方法
int result = calculator.add(a, b);
// 验证结果
assertEquals(5, result, "2 + 3 应该等于 5");
// 额外的断言
assertTrue(result > 0, "结果应该是正数");
}
@Test
@DisplayName("除以零应该抛出异常")
void testDivideByZero() {
// 断言会抛出特定异常
assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
}
@Test
@DisplayName("批量测试加法")
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"10, 20, 30"
})
void testAddMultiple(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
@AfterEach // 每个测试后都会执行
void tearDown() {
System.out.println("清理测试环境...");
calculator = null;
}
}
我学到的第一课:写测试不是可有可无的步骤,而是设计的一部分。
1.3 重构后的可测试代码
我把那个300行的怪物拆分了:
java
// 每个职责独立,可以单独测试
public class OrderService {
private OrderValidator validator;
private InventoryService inventoryService;
private PaymentService paymentService;
// 现在每个方法都可以单独测试
public OrderResult processOrder(Order order) {
validator.validate(order);
inventoryService.reserve(order.getItems());
PaymentResult payment = paymentService.charge(order);
// ...
}
}
// 现在我可以这样测试
@Test
void testOrderValidation() {
Order invalidOrder = createInvalidOrder();
assertThrows(ValidationException.class, () -> {
orderService.processOrder(invalidOrder);
});
}
@Test
void testPaymentFailure() {
Order order = createValidOrder();
mockPaymentServiceToFail(); // 模拟支付失败
OrderResult result = orderService.processOrder(order);
assertFalse(result.isSuccess());
assertEquals("支付失败", result.getMessage());
}
二、反射:第一次发现Java会“读心术”

2.1 那个让我熬夜的配置文件解析器
我需要写一个通用配置加载器,把properties文件映射到Java对象:
java
// 用户配置文件
# application.properties
app.name=我的应用
app.version=1.0.0
server.port=8080
database.url=jdbc:mysql://localhost:3306/test
// 对应的配置类
public class AppConfig {
private String appName;
private String appVersion;
private int serverPort;
private String databaseUrl;
// 一大堆getter和setter...
}
我的第一个版本写了200行硬编码:
java
public AppConfig loadConfig(String filename) {
AppConfig config = new AppConfig();
Properties props = loadProperties(filename);
// 硬编码每个字段的映射
config.setAppName(props.getProperty("app.name"));
config.setAppVersion(props.getProperty("app.version"));
config.setServerPort(Integer.parseInt(props.getProperty("server.port")));
// ... 每加一个字段就要改这里
return config;
}
每次新增配置字段,我都要修改两处:配置类和加载逻辑。
2.2 反射的“顿悟”时刻
直到我发现了反射:
java
public <T> T loadConfig(Class<T> configClass, String filename) {
try {
T config = configClass.newInstance(); // 创建对象
Properties props = loadProperties(filename);
// 获取所有字段
Field[] fields = configClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // 允许访问私有字段
// 根据字段名找配置(约定:驼峰转点分隔)
String propName = camelToDot(field.getName());
String value = props.getProperty(propName);
if (value != null) {
// 根据字段类型转换值
Object convertedValue = convertValue(value, field.getType());
field.set(config, convertedValue); // 设置字段值
}
}
return config;
} catch (Exception e) {
throw new RuntimeException("加载配置失败", e);
}
}
魔法发生了:现在我只需要定义配置类,加载器会自动处理映射。新增字段?只需在类中添加,一行加载代码都不用改!
2.3 反射的实际应用:一个简单的ORM
理解了反射后,我甚至写了个迷你ORM:
java
// 注解:标记数据库表
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String name(); // 表名
}
// 注解:标记字段对应列
public @interface Column {
String name();
boolean nullable() default true;
}
// 实体类
@Table(name = "users")
public class User {
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "username")
private String username;
@Column(name = "email")
private String email;
// getter/setter省略
}
// 使用反射生成SQL
public class SimpleORM {
public String buildInsertSQL(Object entity) {
Class<?> clazz = entity.getClass();
// 获取表名
Table table = clazz.getAnnotation(Table.class);
String tableName = table.name();
// 获取所有字段
StringBuilder columns = new StringBuilder();
StringBuilder values = new StringBuilder();
for (Field field : clazz.getDeclaredFields()) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
field.setAccessible(true);
try {
Object value = field.get(entity);
if (value != null) {
columns.append(column.name()).append(", ");
values.append("'").append(value).append("', ");
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
// 去掉最后的逗号和空格
if (columns.length() > 0) {
columns.setLength(columns.length() - 2);
values.setLength(values.length() - 2);
}
return String.format("INSERT INTO %s (%s) VALUES (%s)",
tableName, columns, values);
}
}
// 使用
User user = new User();
user.setId(1L);
user.setUsername("张三");
user.setEmail("zhangsan@example.com");
String sql = orm.buildInsertSQL(user);
// 输出:INSERT INTO users (id, username, email) VALUES ('1', '张三', 'zhangsan@example.com')
三、动态代理:给代码加上“监控摄像头”

3.1 那个重复的日志代码
我曾经在每个业务方法里都写这样的代码:
java
public class UserService {
public User getUserById(Long id) {
long start = System.currentTimeMillis();
System.out.println("开始查询用户,ID: " + id);
try {
// 业务逻辑
User user = userDao.findById(id);
System.out.println("查询成功,耗时: " + (System.currentTimeMillis() - start) + "ms");
return user;
} catch (Exception e) {
System.out.println("查询失败: " + e.getMessage());
throw e;
}
}
public void updateUser(User user) {
long start = System.currentTimeMillis();
System.out.println("开始更新用户,ID: " + user.getId());
try {
// 业务逻辑
userDao.update(user);
System.out.println("更新成功,耗时: " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
System.out.println("更新失败: " + e.getMessage());
throw e;
}
}
// 每个方法都要重复这个模式...
}
代码重复,难以维护,业务逻辑和横切关注点(日志、性能监控)混在一起。
3.2 JDK动态代理:一次编写,到处记录
java
// 1. 定义接口
public interface UserService {
User getUserById(Long id);
void updateUser(User user);
void deleteUser(Long id);
}
// 2. 实现类(只关注业务逻辑)
public class UserServiceImpl implements UserService {
public User getUserById(Long id) {
// 纯业务逻辑,没有日志代码
return userDao.findById(id);
}
public void updateUser(User user) {
userDao.update(user);
}
public void deleteUser(Long id) {
userDao.delete(id);
}
}
// 3. 代理处理器(统一处理日志)
public class LoggingHandler implements InvocationHandler {
private Object target; // 被代理的对象
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
long start = System.currentTimeMillis();
System.out.println("【开始执行】" + methodName + ",参数: " + Arrays.toString(args));
try {
// 调用原始方法
Object result = method.invoke(target, args);
long duration = System.currentTimeMillis() - start;
System.out.println("【执行成功】" + methodName + ",耗时: " + duration + "ms");
return result;
} catch (Exception e) {
System.out.println("【执行失败】" + methodName + ",错误: " + e.getMessage());
throw e;
}
}
}
// 4. 创建代理对象
public class ServiceFactory {
public static UserService createUserService() {
UserService realService = new UserServiceImpl();
// 创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(), // 类加载器
realService.getClass().getInterfaces(), // 实现的接口
new LoggingHandler(realService) // 代理处理器
);
return proxy;
}
}
// 5. 使用代理
public class Main {
public static void main(String[] args) {
// 获取的是代理对象,不是真实对象
UserService userService = ServiceFactory.createUserService();
// 调用方法时,会自动记录日志
User user = userService.getUserById(1L);
userService.updateUser(user);
}
}
神奇的效果:现在我的业务代码很干净,所有方法自动拥有了日志、性能监控、事务管理等功能。
3.3 动态代理的更多应用场景
java
// 权限检查代理
public class SecurityHandler implements InvocationHandler {
private Object target;
private User currentUser;
public SecurityHandler(Object target, User currentUser) {
this.target = target;
this.currentUser = currentUser;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 检查注解
RequiresPermission permission = method.getAnnotation(RequiresPermission.class);
if (permission != null) {
String requiredRole = permission.value();
if (!currentUser.hasRole(requiredRole)) {
throw new SecurityException("权限不足,需要角色: " + requiredRole);
}
}
return method.invoke(target, args);
}
}
// 缓存代理
public class CacheHandler implements InvocationHandler {
private Object target;
private Map<String, Object> cache = new HashMap<>();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 生成缓存键:方法名+参数
String cacheKey = generateCacheKey(method, args);
// 检查缓存
if (cache.containsKey(cacheKey)) {
System.out.println("【缓存命中】" + cacheKey);
return cache.get(cacheKey);
}
// 调用真实方法
Object result = method.invoke(target, args);
// 放入缓存
cache.put(cacheKey, result);
System.out.println("【缓存设置】" + cacheKey);
return result;
}
}
四、组合使用:一个迷你Spring框架

当我把这些技术组合起来时,我仿佛看到了Spring框架的雏形:
java
// 1. 定义一些“Spring风格”的注解
@Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
@Autowired
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
// 2. 容器类(迷你IoC容器)
public class MiniContainer {
private Map<String, Object> beans = new HashMap<>();
public void register(Class<?>... componentClasses) {
for (Class<?> clazz : componentClasses) {
if (clazz.isAnnotationPresent(Component.class)) {
try {
// 创建实例
Object instance = clazz.newInstance();
// 获取组件名
Component component = clazz.getAnnotation(Component.class);
String beanName = component.value();
if (beanName.isEmpty()) {
beanName = clazz.getSimpleName();
}
// 存入容器
beans.put(beanName, instance);
System.out.println("注册Bean: " + beanName + " -> " + clazz.getName());
} catch (Exception e) {
throw new RuntimeException("创建组件失败: " + clazz.getName(), e);
}
}
}
// 依赖注入
injectDependencies();
}
private void injectDependencies() {
for (Object bean : beans.values()) {
Class<?> clazz = bean.getClass();
// 注入字段
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
// 按类型查找依赖
Object dependency = findBeanByType(field.getType());
if (dependency != null) {
try {
field.set(bean, dependency);
System.out.println("注入依赖: " + clazz.getSimpleName() +
"." + field.getName() + " <- " +
dependency.getClass().getSimpleName());
} catch (IllegalAccessException e) {
throw new RuntimeException("注入依赖失败", e);
}
}
}
}
}
}
@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> requiredType) {
return (T) findBeanByType(requiredType);
}
private Object findBeanByType(Class<?> requiredType) {
for (Object bean : beans.values()) {
if (requiredType.isAssignableFrom(bean.getClass())) {
return bean;
}
}
return null;
}
}
// 3. 定义一些组件
@Component("userService")
public class UserService {
@Autowired
private UserRepository userRepository; // 会自动注入
public User findUser(Long id) {
return userRepository.findById(id);
}
}
@Component("userRepository")
public class UserRepository {
public User findById(Long id) {
// 模拟数据库查询
return new User(id, "张三");
}
}
// 4. 使用迷你容器
public class Application {
public static void main(String[] args) {
MiniContainer container = new MiniContainer();
// 注册组件(自动创建实例和注入依赖)
container.register(UserService.class, UserRepository.class);
// 获取Bean
UserService userService = container.getBean(UserService.class);
// 使用
User user = userService.findUser(1L);
System.out.println("找到用户: " + user.getName());
}
}
运行这个程序,你会看到:
text
注册Bean: userService -> UserService
注册Bean: userRepository -> UserRepository
注入依赖: UserService.userRepository <- UserRepository
找到用户: 张三
五、经验总结:从“能用”到“好用”的转变
5.1 我学到的编程哲学
- 测试驱动设计:如果一个方法难以测试,通常意味着设计有问题
- 关注点分离:业务逻辑、日志、安全、事务应该分开处理
- 约定优于配置:通过注解和反射,减少重复的配置代码
- 动态扩展:通过代理模式,在不修改源码的情况下增强功能
5.2 实际项目中的使用建议
单元测试:
- 先写测试,再写实现(TDD)
- 每个测试只测一个功能点
- 使用Mock对象隔离依赖
反射:
- 框架开发中使用,业务代码中慎用
- 注意性能影响,适当缓存反射结果
- 处理好各种异常情况
注解:
- 定义清晰的命名和默认值
- 提供详细的文档说明
- 考虑向后兼容性
动态代理:
- 接口代理用JDK动态代理
- 类代理用CGLIB
- Spring AOP已经封装得很好,直接使用即可
5.3 一个简单的检查清单
当你写代码时,问自己这些问题:
- 我的代码容易写单元测试吗?
- 有没有重复的横切关注点(日志、事务)可以抽取?
- 是否可以通过注解减少配置?
- 是否需要在不修改源码的情况下扩展功能?
结语:从“写代码”到“设计代码”

学习这些高级特性之前,我只是在“写代码”——让程序运行起来就行。
学习之后,我开始“设计代码”——思考如何让代码更可测试、更可维护、更可扩展。
最大的收获不是学会了反射或代理的技术细节,而是学会了思考代码的“元”问题:这段代码未来会怎样变化?别人如何理解和使用它?如何让它适应未知的需求?
现在,当我看到@Autowired时,我不再觉得这是魔法。我知道这背后是反射、是动态代理、是设计模式的巧妙组合。更重要的是,我知道何时使用这些技术,何时保持简单。
记住:最酷的技术不是最复杂的技术,而是最合适的技术。 有时候,一个简单的设计比过度工程化的“高级”方案更有价值。
知识点测试
读完文章了?来测试一下你对知识点的掌握程度吧!
评论区
使用 GitHub 账号登录后即可发表评论,支持 Markdown 格式。
如果评论系统无法加载,请确保:
- 您的网络可以访问 GitHub
- giscus GitHub App 已安装到仓库
- 仓库已启用 Discussions 功能